/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "CameraPerspective.h"
#include <xml/Stream.h>
#include <safe_new.h>
#include <Converter.h>
#include <string>
#include <lib3d/Math_lib3d.h>
#include <boost/bind.hpp>

///using namespace math;

namespace liba
{
namespace lib3d
{
namespace node
{

const Atom CameraPerspective::node_name( "CameraPerspective" );
const Atom CameraPerspective::aspect_name( "Aspect" );
const Atom CameraPerspective::fov_name( "Fov" );

CameraPerspective::CameraPerspective(const Atom & name, Node * parent )
:	Camera( name, parent ),
	aspect( 3.0/4.0 ),
	fov( 0.9273 )
{
	node_type = node_name;

	this->RegisterProperty( fov_name, &this->fov );
	this->RegisterProperty( aspect_name, &this->aspect );

	this->fov.Changed().connect( boost::bind( &CameraPerspective::OnChangeParameters<double>, this, _1 ) );
	this->aspect.Changed().connect( boost::bind( &CameraPerspective::OnChangeParameters<double>, this, _1 ) );
}

CameraPerspective * CameraPerspective::clone()
{
	return new CameraPerspective(*this);
}

bool CameraPerspective::on_attribute( xml::Provider * prov, const std::string & name, const std::string & value )
{
	if ( name == "Aspect" )
		return this->aspect.FromString( value );
	if ( name == "Fov" )
		return this->fov.FromString(value);
	return Camera::on_attribute( prov, name, value );
}

void CameraPerspective::change_aspect( double aspect )
{
	this->aspect = aspect;
	on_change_parameters();
}

void CameraPerspective::change_fov( double fov )
{
	this->fov = fov;
	on_change_parameters();
}

void CameraPerspective::on_change_parameters()
{
	/** @todo calculate frustrum */
	desc.projection = calculate_perspective_projection_matrix( fov, aspect, desc.near_plane, desc.far_plane );

	double h = ( cos( fov/2 ) / sin( fov/2 ) );
	double w = aspect * h;
	desc.frustrum = math::Frustrum(desc.near_plane, desc.far_plane, 2*atan(1/w), fov);
}

Ray CameraPerspective::get_world_ray(double ray_x, double ray_y)
{
//	Vector proj_pos( ray_x, ray_y, 1 );
//	Vector cam_pos = Matrix::invert(desc.projection) * proj_pos;
	double h = ( cos( fov/2 ) / sin( fov/2 ) );
	double w = aspect * h;
	Vector cam_pos = Vector( ray_x / w, ray_y / h, 1 );
	Vector world_pos = GetGlobalMatrix() * cam_pos;
	Vector center_pos = GetGlobalMatrix().shift();
	return Ray(center_pos, world_pos - center_pos);
}

} // namespace node
} // namespace lib3d
} // namespace liba

